#include "http_request.h"

#include <set>

#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>

#include <openssl/md5.h>

#include "ImServiceCommon_types.h"
#include "base/logging.h"
#include "base/url_encode.h"
#include "base/format_time.h"

#include "uid_session_map.h"
#include "http_service.h"

using namespace std;
namespace imserver {

bool ParseKeyValue(const char * data, const char * key_seps,
    char kv_sep, std::map<std::string, std::string> * pairs) {
  if (!data)
    return false;

  std::vector<std::string> strs;
  boost::split(strs, data, boost::is_any_of(key_seps),
      boost::token_compress_on);
  for (size_t i = 0; i < strs.size(); ++i) {
    size_t pos = strs[i].find_first_of(kv_sep);
    if (pos != std::string::npos) {
      (*pairs)[strs[i].substr(0, pos)] = base::UrlDecode(strs[i].substr(pos + 1));
    } else {
      (*pairs)[strs[i]] = "";
    }
  }
  return true;
}

HttpRequest::~HttpRequest() {
}

size_t HttpRequest::AppendBody(const char * data, size_t len) {
  try {
    body_.append(data, len);
  } catch (std::exception & e) {
    LOG_WARN("AppendBody string append err");
  }
  return body_.size();
}

std::string HttpRequest::ParseCallback() {
  std::string callback = query_param("callback");
  static const boost::regex e("[a-zA-Z_][a-zA-Z0-9_.]*");
  if (!callback.empty() && boost::regex_match(callback, e)) {
    return callback;
  }
  static const std::string empty;
  return empty;
}

std::string HttpRequest::CalculateSession(int64_t uid, const std::string & q_ticket, const std::string & t_ticket) {
  static std::string kSessionSalt = "xnp%%l?0$thri9x\r\nxv!66SFqxh3gooa2#D4@8zR34!";
  std::stringstream ss;
  ss << kSessionSalt << uid;
  if (uid < IM_TEMP_ID_BASE) {
    // 动态session, 每次分配后都变
    ss << q_ticket << t_ticket << (time(NULL) - 31127) << '_' << g_shard_id;
  } else {
    // 临时id的session规则固定, 持久有效
    ss << (uid * 3 % 119873) << "well,let's_add_more_salt_simida.$94180256";
  }

  unsigned char digest[MD5_DIGEST_LENGTH];
  
  MD5((unsigned char*)ss.str().c_str(), ss.str().size(), (unsigned char*)&digest);

  std::stringstream res;
  for (size_t i = 0; i < MD5_DIGEST_LENGTH; ++i) {
    res << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)digest[i];
  }
  res << '_' << g_shard_id;
  return res.str();
}

std::string HttpRequest::IdCookiesHeader(
      int64_t qid,
      const std::string & qid_ticket,
      int64_t temp_id,
      const std::string & temp_id_ticket) {
  std::stringstream header;

  if (qid > 0) {
    header << "Set-Cookie: " << IM_QID_COOKIE << "=" << qid << "; Domain=.bang.360.cn; Path=/\r\n";
    header << "Set-Cookie: " << IM_QID_TICKET_COOKIE << "=" << qid_ticket << "; Domain=.bang.360.cn; Path=/\r\n";
    // 若 qid 有限，则临时id完全忽略。注意此时不要销毁临时id
    return header.str();
  } else {
    header << "Set-Cookie: " << IM_QID_COOKIE << "=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Domain=.bang.360.cn; Path=/\r\n";
    header << "Set-Cookie: " << IM_QID_TICKET_COOKIE << "=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Domain=.bang.360.cn; Path=/\r\n";
  }

  std::string expire = base::GmtTime(time(NULL) + 30 * 86400);

  if (temp_id > 0) {
    header << "Set-Cookie: " << IM_TEMP_ID_COOKIE << "=" << temp_id << "; Expires=" << expire << "; Domain=.bang.360.cn; Path=/\r\n";
    header << "Set-Cookie: " << IM_TEMP_ID_TICKET_COOKIE << "=" << temp_id_ticket <<  "; Expires=" << expire << "; Domain=.bang.360.cn; Path=/\r\n";
  } else {
    header << "Set-Cookie: " << IM_TEMP_ID_COOKIE << "=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Domain=.bang.360.cn; Path=/\r\n";
    header << "Set-Cookie: " << IM_TEMP_ID_TICKET_COOKIE << "=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Domain=.bang.360.cn; Path=/\r\n";
  }
  return header.str();
}

int HttpRequest::UnauthorizedResponse() {
  std::stringstream rsp_header;
  std::stringstream body;

  std::string callback = ParseCallback();
  if (!callback.empty()) {
    // 开头空格是 IE bug fix
    body << ' ' << callback << "(";
  }
  body << "{type:" << FORBIDDEN << ", msg:'Authorization required.'}";
  rsp_header << "HTTP/1.1 401 Unauthorized\r\n"
             << IdCookiesHeader(0, "", 0, "")
             << "Content-Type: text/javascript; charset=utf-8\r\n"
             << "Connection: close\r\n"
             << "Content-Length: " << body.str().size() << "\r\n"
             << "\r\n";

  response_ = rsp_header.str();
  response_.append(body.str());

  if (!callback.empty()) {
    body << ")";
  }
  connection_->WriteResponse(response_);
  return 0;
}

void HttpRequest::Abort() {
  LOG_INFO("request abroted, uid=" << effective_uid() << " sesssion=" << effective_session());
  connection_->Abort();
}

// 返回值:
//   0，表示check完毕, 本地调用即可判断是否合法
//   非0，表示本地check不足以验证合法性，需要调用session service
int HttpRequest::CheckTicket() {
  // 先本地过一下所有的临时ticket
  if (temp_id_ > 0) {
    if (UidSessionMap::Instance().Contains(temp_id_, temp_id_ticket_)) {
      LOG_INFO("temp_id CheckTicket 1, " << temp_id_ticket_ << '/' << temp_id_);
    } else {
      std::string ticket = CalculateSession(temp_id_, "", "");
      if (temp_id_ticket_.substr(0, MD5_DIGEST_LENGTH) == ticket.substr(0, MD5_DIGEST_LENGTH)) {
        LOG_INFO("temp_id CheckTicket 1, " << temp_id_ticket_ << '/' << temp_id_);
        temp_id_ticket_ = ticket;
      } else {
        LOG_INFO("temp_id CheckTicket 0, " << temp_id_ticket_ << "/" << temp_id_ << " expected=" << ticket);
        temp_id_ = 0;
      }
    }
  }

  if (qid_ > 0) {
    if (UidSessionMap::Instance().Contains(qid_, qid_ticket_)) {
      LOG_INFO("qid CheckTicket 1, " << qid_ticket_ << '/' << qid_);
      return 0; // 本接入登录用户, 不需要再验证临时id
    } else if (qid_ticket_.size() <= MD5_DIGEST_LENGTH) {
      qid_ = 0;
      LOG_INFO("qid CheckTicket 0, " << qid_ticket_ << '/' << qid_);
    } else {
      LOG_INFO("qid CheckTicket pending, " << qid_ticket_ << '/' << qid_);
      return 1; // 本接入内无法判断
    }
  }

  if (!cookie("Q").empty() || !cookie("T").empty()) {
    LOG_INFO("qt CheckTicket pending, q=" << cookie("Q") << " t=" << cookie("T"));
    return 2;
  }

  return 0;
}


size_t HttpRequest::content_length() const {
  if (method_ != "POST") {
    return 0;
  }

  std::map<std::string, std::string>::const_iterator it = headers_.find("Content-Length");
  if (it == headers_.end()) {
    return 0;
  }

  try {
    return boost::lexical_cast<size_t>(it->second);
  } catch (boost::bad_lexical_cast &) {
    return 0;
  }
}

bool HttpRequest::IsHeaderCompleted(const char * data, size_t len) {
  return std::string::npos != std::string(data, len).find("\r\n\r\n");
}

HttpRequest::HttpRequest(CometConnectionPtr conn, const std::string & data) 
    : connection_(conn), qid_(0), temp_id_(0) {
  // cout << "data= " << data<< endl;
  size_t end_of_header = data.find("\r\n\r\n");

  if (end_of_header == std::string::npos) {
    // cout << "header not complete" << endl;
    return;
  }

  size_t pos = data.find("\r\n");
  std::string request_line = data.substr(0, pos);

  std::vector<std::string> fields;
  boost::split(fields, request_line, boost::is_any_of(" "), boost::token_compress_on);
  if (fields.size() != 3) {
    // cout << "fields size != 3" << fields.size() << endl;
    return;
  }
  method_ = fields[0];

  size_t url_end_pos = fields[1].find_first_of('?');
  url_ = fields[1].substr(0, url_end_pos);
  if (url_end_pos != std::string::npos) {
    query_ = fields[1].substr(url_end_pos + 1);
  }

  version_ = fields[2];
  
  while(pos < end_of_header - 4) {
    size_t next_pos = data.find("\r\n", pos + 2);
    if (next_pos == std::string::npos) {
      break;
    }
    std::string line = data.substr(pos + 2, next_pos - pos - 2);
    size_t sp = line.find(": ");
    if (sp != std::string::npos && sp > 0 && sp < line.size() - 2) {
      headers_[line.substr(0, sp)] = line.substr(sp + 2);
      // cout << "header (" << line.substr(0, sp) << ")=(" << line.substr(sp + 2) << ")" << endl;
    }

    pos = next_pos;
  }

  //parse query_string
  if (!query_.empty()) {
    ParseKeyValue(query_.c_str(), "&", '=', &query_params_);
  }

  //parse cookie
  std::map<std::string,std::string>::const_iterator it = headers_.find("Cookie");
  if (it != headers_.end()) {
    ParseKeyValue(it->second.c_str(), "; ", '=', &cookies_);
  }

  body_ = data.substr(end_of_header + 4);

  try {
    qid_ = boost::lexical_cast<int64_t>(cookie(IM_QID_COOKIE));
  } catch (boost::bad_lexical_cast&) {}
  qid_ticket_ = cookie(IM_QID_TICKET_COOKIE);

  try {
    temp_id_ = boost::lexical_cast<int64_t>(cookie(IM_TEMP_ID_COOKIE));
  } catch (boost::bad_lexical_cast&) {}
  temp_id_ticket_ = cookie(IM_TEMP_ID_TICKET_COOKIE);

//std::cout << "method = " << method_ << "\r\n";
//std::cout << "url = " << url_ << "\r\n";
//std::cout << "verions = " << version_ << "\r\n";
//std::cout << "headers.size() = " << headers_.size() << "\r\n";
//std::cout << "cookies.size() = " << cookies_.size() << "\r\n";
//std::cout << "query_params.size() = " << query_params_.size() << "\r\n";
//std::cout << "query_ = " << query_ << "\r\n";
//std::cout << "body = " << body_ << "\r\n" << std::endl;
}
void HttpRequest::OnReadComplete() {
  if (method_ == "POST" && !body_.empty()) {
    ParseKeyValue(body_.c_str(), "&", '=', &post_params_);
  }
//std::cout << "OnReadComplete." << std::endl;
//std::cout << "post_params_ / body_  = ----------" << body_ << "\r\n----------" << std::endl;
//std::cout << "post_param.size() = " << post_params_.size() << "\r\n" << std::endl;
  Response();
}

bool HttpRequest::IsLegal() const {
  if (method_ != "GET" && method_ != "POST") {
    LOG_WARN("illegal req, bad method=" << method_);
    return false;
  }

  if (url_.empty()) {
    LOG_WARN("illegal req, empty url");
    return false;
  }

  if (version_ != "HTTP/1.0" && version_ != "HTTP/1.1") {
    LOG_WARN("illegal req, version_=" << version_);
    return false;
  }
  if (method_ == "GET" && !body_.empty()) {
    return false;
  }
  return true;
}

std::ostream& operator<<(HttpRequest & req, std::ostream& os) {
  os << "method = " << req.method_ << "\r\n";
  os << "url = " << req.url_ << "\r\n";
  os << "verions = " << req.version_ << "\r\n";
  os << "headers.size() = " << req.headers_.size() << "\r\n";
  os << "body = " << req.body_ << "\r\n";
  return os;
}

}

